/**
 *
 *  comment here
 *
 */


#include <g-bios.h>
#include <net/net.h>
#include <core/io.h>
#include <core/timer.h>
#include <net/mii.h>
#include <core/irq.h>
#include <arch/arm.h>
#include "dm9000.h"


static int DM9000GetLinkStatus(void);


static BYTE DM9000Read(BYTE bRegIdx)
{
	WriteByte(bRegIdx, DM9000_INDEX_PORT);
	return ReadByte(DM9000_DATA_PORT);
}


static void DM9000Write(BYTE bRegIdx, BYTE bValue)
{
	WriteByte(bRegIdx, DM9000_INDEX_PORT);
	WriteByte(bValue, DM9000_DATA_PORT);
}


static UINT16 DM9000PhyRead(BYTE bRegIdx)
{
	UINT16 hVal;

	DM9000Write(DM9000_EPAR, DM9000_INTER_PHY | bRegIdx);
	DM9000Write(DM9000_EPCR, 0xc); // fixme

	udelay(100); // fixme

	DM9000Write(DM9000_EPCR, 0x0);

	hVal = (DM9000Read(DM9000_EPDRH) << 8) | DM9000Read(DM9000_EPDRL);

	return hVal;
}


static void DM9000PhyWrite(BYTE bRegIdx, UINT16 hVal)
{
	DM9000Write(DM9000_EPAR, DM9000_INTER_PHY | bRegIdx);

	DM9000Write(DM9000_EPDRL, hVal & 0xff);
	DM9000Write(DM9000_EPDRH, (hVal >> 8) & 0xff);
	DM9000Write(DM9000_EPCR, 0xa); // fixme

	udelay(100); // fixme!

	DM9000Write(DM9000_EPCR, 0x0);
}


static int DM9000SetMacAddr(struct NetDevice *pNetDev, const BYTE *pMac)
{
	int i;

	for (i = 0; i < MAC_ADDR_LEN; i++)
	{
		pNetDev->vbMacAddr[i] = pMac[i];
		DM9000Write(DM9000_PAR + i, pNetDev->vbMacAddr[i]);
	}

	return 0;
}


static int DM9000Reset(void)
{
	int i;
	BYTE bStat;

	DM9000Write(DM9000_NCR, 1); 
	while (DM9000Read(DM9000_NCR) & 1);

	bStat = DM9000Read(DM9000_ISR);
	DM9000Write(DM9000_ISR, bStat);

	DM9000Write(DM9000_TCR, 0);	
	DM9000Write(DM9000_RCR, 0);
	DM9000Write(DM9000_BPTR, 0x3f);
	DM9000Write(DM9000_FCTR, 0x38);
	DM9000Write(DM9000_SMCR, 0);

	for (i = 0; i < 8; i++)
		DM9000Write(DM9000_MAR + i, 0x0);

	DM9000Write(DM9000_GPR, 0x00); //

	return 0;
}


static int DM9000SendPacket(struct NetDevice *pNetDev, struct SocketBuffer *pSockBuff)
{
	int i;
	const UINT16 *phTxData;
	UINT16 nTxSize;
	unsigned int tmo, timeout;
	ULONG ulFlag;

	LockIrqPsr(ulFlag);

	WriteByte(DM9000_MWCMD, DM9000_INDEX_PORT);

	phTxData = (const UINT16 *)pSockBuff->pSkbData;
	nTxSize = pSockBuff->wSkbSize;

	for (i = 0; i < nTxSize; i += sizeof(*phTxData))
	{
		WriteShort(*phTxData, DM9000_DATA_PORT);
		phTxData++;
	}

	DM9000Write(DM9000_TXPLL, nTxSize & 0xff);
	DM9000Write(DM9000_TXPLH, (nTxSize >> 8) & 0xff);

	DM9000Write(DM9000_TCR, 1);

	while (DM9000Read(DM9000_TCR) & 1);

	UnlockIrqPsr(ulFlag);

	return 0;
}

static int DM9000RecvPacket(void)
{
	int i;
	BYTE bVal;
	UINT16 *phRxData;
	UINT16 hRxStat, hRxSize;
	struct SocketBuffer *pSockBuff;


	while (1)
	{
		DM9000Read(DM9000_MRCMDX);
		bVal = ReadByte(DM9000_DATA_PORT);

		if (bVal != 0x1) 
		{
			if (bVal < 0x1)
				return 0;

			printf("\n%s(), line %d: wrong status = 0x%02x\n",
				__FUNCTION__, __LINE__, bVal);

			DM9000Reset();
			DM9000Write(DM9000_RCR, 1);			
			DM9000Write(DM9000_IMR, IMR_VAL);

			return -EIO;
		}

		WriteByte(DM9000_MRCMD, DM9000_INDEX_PORT);
		hRxStat = ReadShort(DM9000_DATA_PORT);
		hRxSize = ReadShort(DM9000_DATA_PORT);

		// TODO: check the size

		if ((hRxStat & 0xbf00) || (pSockBuff = GkSockBuffAlloc(0, (hRxSize + 1) & ~1)) == NULL)
		{
			for (i = 0; i < hRxSize; i += 2)
			{
				ReadShort(DM9000_DATA_PORT);
			}

			// printf("\n%s(), line %d error: status = 0x%04x, size = %d\n",
			// 		__FUNCTION__, __LINE__, hRxStat, hRxSize);
		}
		else
		{
			phRxData  = (UINT16 *)pSockBuff->pSkbData;  // or pSkbHead;

			for (i = 0; i < hRxSize; i += 2)
			{
				*phRxData = ReadShort(DM9000_DATA_PORT);
				phRxData++;
			}

			pSockBuff->wSkbSize -= 4;

			GkNetRxDcp(pSockBuff);
		}
	}

	return 0;
}


static int DM9000GetLinkStatus(void)
{
	int i;

	for(i = 0; i < 0x10000; i++)
	{
	 	if (DM9000PhyRead(MII_REG_BMS) & 0x20)
			goto L1;

		udelay(0x10);
	}

	return -EIO;

L1:
	switch (DM9000PhyRead(MII_REG_STAT) >> 12)
	{
	case 1:
		return ETHER_SPEED_10M_HD;

	case 2:
		return ETHER_SPEED_10M_FD;

	case 4:
		return ETHER_SPEED_100M_HD;

	case 8:
		return ETHER_SPEED_100M_FD;

	default:
		break;
	}

	return ETHER_SPEED_UNKNOW;
}


static int DM9000Isr(void *pDev)
{
	BYTE bStat;

	bStat = DM9000Read(DM9000_ISR);
	DM9000Write(DM9000_ISR, bStat);

	if (bStat & 0x1)
	{
		DM9000RecvPacket();
	}

#if 0
	if (bStat & 0x20)
	{
		printf("dm9000 link status changed!\n");
		// TODO: pull or plug ?
	}
#endif

	return 0;
}


static __INIT__ int DM9000Init(void)
{
	int    i, iSpeed;
	UINT16 hVID, hDID, hPhyID2;
	BYTE   bStat;
	struct NetDevice *pNetDev;


	DRIVER_INFO("DM9000\n");

	for (i = 0; i < 10; i++)
	{
		hVID = (DM9000Read(DM9000_VIDH) << 8) | DM9000Read(DM9000_VIDL);
		hDID = (DM9000Read(DM9000_PIDH) << 8) | DM9000Read(DM9000_PIDL);

		if (hVID == VENDOR_ID_DAVICOM)
			goto L1;
	}

	printf("Wrong IDs: 0x%04x, 0x%04x\n", hVID, hDID);

	return -ENODEV;

L1:
	hPhyID2 = DM9000PhyRead(MII_REG_ID2);

	printf("DM900x ID = 0x%x, Varient = 0x%x, Rev = %d.\n",
			hDID, hPhyID2 >> 4, hPhyID2 & 0xf);

	DM9000Reset();

	printf("Detecting ethernet speed ... ");

	iSpeed = DM9000GetLinkStatus();

	if (iSpeed < 0)
	{
		printf("NOT linked, please check the cable!\n");
	}
	else
	{
		switch(iSpeed)
		{
		case ETHER_SPEED_10M_HD:
			printf("10M Half Duplex activated!\n");
			break;
	
		case ETHER_SPEED_10M_FD:
			printf("10M Full Duplex activated!\n");
			break;
	
		case ETHER_SPEED_100M_HD:
			printf("100M Half Duplex activated!\n");
			break;
	
		case ETHER_SPEED_100M_FD:
			printf("100M Full Duplex activated!\n");
			break;

		default:
			printf("Unknown speed (%d)!\n", iSpeed);
			break;
		}
	}

	pNetDev = GkNetDevNew();
	BUG_IF (NULL == pNetDev);

	RegisterIsr(CONF_DM9000_IRQ, DM9000Isr, NULL);

	// TODO:  try to manually update when RX/TX done
	DM9000Write(DM9000_IMR, IMR_VAL);
	DM9000Write(DM9000_RCR, 1);

	pNetDev->SendPacket = DM9000SendPacket;
	pNetDev->SetMacAddr = DM9000SetMacAddr;

	return GkNetDevRegister(pNetDev);
}


DRIVER_INIT(DM9000Init);

